package com.asgard.utils;

import org.apache.commons.lang3.Validate;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;

/**
 * 该类封装了序列化/反序列化的工具函数。
 */
public class SerializeUtils {

    /**
     * 读取输入流中的下一个整数，整数采用variable length integer的编码方式。
     *
     * @param sin 输入流
     * @return 流中的下一个整数
     * @throws IOException 输入流读取错误
     */
    public static long readVarInt(InputStream sin) throws IOException {
        try {
            int b = sin.read();
            if (b < 0xFD)
                return b;
            if (b == 0xFD) {
                byte[] data = new byte[4];
                sin.read(data, 0, 2);
                return ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).getInt();
            }
            if (b == 0xFE) {
                byte[] data = new byte[8];
                sin.read(data, 0, 4);
                return ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).getLong();
            }
            if (b == 0xFF) {
                byte[] data = new byte[8];
                sin.read(data, 0, 8);
                return ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).getLong();
            }
            return 0;
        } catch (Exception e) {
            throw new IOException("字节流格式不正确", e);
        }
    }

    /**
     * 将整数写入输出流中，整数采用variable length integer的编码方式。
     *
     * @param sout 输出流
     * @param val  整数值，必须为非负数
     * @throws IOException 输出流写入错误
     */
    public static void writeVarInt(OutputStream sout, long val) throws IOException {
        Validate.validState(val >= 0);
        if (val < 0xFD) {
            sout.write((int) val);
            return;
        }
        byte[] data = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(val).array();
        if (val <= 0xFFFF) {
            sout.write(0xFD);
            sout.write(data, 0, 2);
        } else if (val <= 0xFFFF_FFFF) {
            sout.write(0xFE);
            sout.write(data, 0, 4);
        } else {
            sout.write(0xFF);
            sout.write(data, 0, 8);
        }
    }

    /**
     * 读取输入流中的下一个字符串，字符串采用variable length string的编码方式。
     *
     * @param sin 输入流
     * @return 流中的下一个字符串
     * @throws IOException 输入流读取错误
     */
    public static String readVarString(InputStream sin) throws IOException {
        try {
            int len = (int) readVarInt(sin);
            if (len < 0 || len > sin.available()) throw new IOException("字节流长度不正确");
            byte[] bytes = new byte[len];
            sin.read(bytes, 0, len);
            return new String(bytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new IOException("字节流格式不正确", e);
        }
    }

    /**
     * 将字符串写入输出流中，字符串采用variable length string的编码方式。
     *
     * @param sout 输出流
     * @param str  字符串，不允许为null
     * @throws IOException 输出流写入错误
     */
    public static void writeVarString(OutputStream sout, String str) throws IOException {
        Validate.notNull(str);
        byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
        writeVarInt(sout, bytes.length);
        sout.write(bytes);
    }

    /**
     * 读取输入流中的下一个字节数组，字节数组采用variable length byte array的编码方式。
     *
     * @param sin 输入流
     * @return 流中的下一个字节数组
     * @throws IOException 输入流读取错误
     */
    public static byte[] readVarByteArray(InputStream sin) throws IOException {
        try {
            int len = (int) readVarInt(sin);
            if (len < 0 || len > sin.available()) throw new IOException("字节流长度不正确");
            byte[] bytes = new byte[len];
            sin.read(bytes, 0, len);
            return bytes;
        } catch (Exception e) {
            throw new IOException("字节流格式不正确", e);
        }
    }

    /**
     * 将字节数组写入输出流中，字节数组采用variable length byte array的编码方式。
     *
     * @param sout  输出流
     * @param bytes 字节数组，不允许为null
     * @throws IOException 输出流写入错误
     */
    public static void writeVarByteArray(OutputStream sout, byte[] bytes) throws IOException {
        Validate.notNull(bytes);
        writeVarInt(sout, bytes.length);
        sout.write(bytes);
    }

    /**
     * 读取输入流中的下一个整数，整数采用默认编码方式。
     *
     * @param sin 输入流
     * @return 流中的下一个整数
     * @throws IOException 输入流读取错误
     */
    public static long readLong(InputStream sin) throws IOException {
        try {
            byte[] data = new byte[8];
            sin.read(data, 0, 8);
            return ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).getLong();
        } catch (Exception e) {
            throw new IOException("字节流格式不正确", e);
        }
    }

    /**
     * 将整数写入输出流中，整数采用默认编码方式。
     *
     * @param sout 输出流
     * @param val  整数值
     * @throws IOException 输出流写入错误
     */
    public static void writeLong(OutputStream sout, long val) throws IOException {
        byte[] data = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(val).array();
        sout.write(data, 0, 8);
    }


    /**
     * 读取输入流中的下一个整数，整数采用默认编码方式。
     *
     * @param sin 输入流
     * @return 流中的下一个整数
     * @throws IOException 输入流读取错误
     */
    public static int readInt(InputStream sin) throws IOException {
        try {
            byte[] data = new byte[4];
            sin.read(data, 0, 4);
            return ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).getInt();
        } catch (Exception e) {
            throw new IOException("字节流格式不正确", e);
        }
    }

    /**
     * 将整数写入输出流中，整数采用默认编码方式。
     *
     * @param sout 输出流
     * @param val  整数值
     * @throws IOException 输出流写入错误
     */
    public static void writeInt(OutputStream sout, int val) throws IOException {
        byte[] data = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(val).array();
        sout.write(data, 0, 4);
    }

    /**
     * 读取输入流中的下一个字符串，字符串采用默认编码方式。
     *
     * @param sin 输入流
     * @return 流中的下一个字符串
     * @throws IOException 输入流读取错误
     */
    public static String readString(InputStream sin) throws IOException {
        try {
            int len = (int) readInt(sin);
            if (len < 0) return null;
            if (len > sin.available()) throw new IOException("字节流长度不正确");
            byte[] bytes = new byte[len];
            sin.read(bytes, 0, len);
            return new String(bytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new IOException("字节流格式不正确", e);
        }
    }

    /**
     * 将字符串写入输出流中，字符串采用默认编码方式。
     *
     * @param sout 输出流
     * @param str  字符串
     * @throws IOException 输出流写入错误
     */
    public static void writeString(OutputStream sout, String str) throws IOException {
        if (str == null) {
            writeInt(sout, -1);
            return;
        }
        byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
        writeInt(sout, bytes.length);
        sout.write(bytes);
    }

    /**
     * 读取输入流中的下一个字节数组，字节数组采用默认编码方式。
     *
     * @param sin 输入流
     * @return 流中的下一个字节数组
     * @throws IOException 输入流读取错误
     */
    public static byte[] readByteArray(InputStream sin) throws IOException {
        try {
            int len = readInt(sin);
            if (len < 0) return null;
            if (len > sin.available()) throw new IOException("字节流长度不正确");
            byte[] bytes = new byte[len];
            sin.read(bytes, 0, len);
            return bytes;
        } catch (Exception e) {
            throw new IOException("字节流格式不正确", e);
        }
    }

    /**
     * 将字节数组写入输出流中，字节数组采用默认编码方式。
     *
     * @param sout  输出流
     * @param bytes 字节数组
     * @throws IOException 输出流写入错误
     */
    public static void writeByteArray(OutputStream sout, byte[] bytes) throws IOException {
        if (bytes == null) {
            writeInt(sout, -1);
            return;
        }
        writeInt(sout, bytes.length);
        sout.write(bytes);
    }

    /**
     * 读取输入流中的下一个布尔值。
     *
     * @param sin 输入流
     * @return 流中的下一个布尔值
     * @throws IOException 输入流读取错误
     */
    public static boolean readBoolean(InputStream sin) throws IOException {
        try {
            int v = sin.read();
            return v != 0;
        } catch (Exception e) {
            throw new IOException("字节流格式不正确", e);
        }
    }

    /**
     * 将布尔值写入到输出流中。
     *
     * @param sout 输出流
     * @param val  布尔值
     * @throws IOException 输出流写入错误
     */
    public static void writeBoolean(OutputStream sout, boolean val) throws IOException {
        sout.write(val ? 1 : 0);
    }

    /**
     * 读取输入流中的下一个大整数值。
     *
     * @param sin 输入流
     * @return 流中的下一个大整数值
     * @throws IOException 输入流读取错误
     */
    public static BigInteger readBigInteger(InputStream sin) throws IOException {
        try {
            return new BigInteger(readByteArray(sin));
        } catch (Exception e) {
            throw new IOException("字节流格式不正确", e);
        }
    }

    /**
     * 将大整数值写入到输出流中。
     *
     * @param sout 输出流
     * @param val  大整数值
     * @throws IOException 输出流写入错误
     */
    public static void writeBigInteger(OutputStream sout, BigInteger val) throws IOException {
        writeByteArray(sout, val.toByteArray());
    }

    /**
     * 将int整数转换为4个字节的字节数组。
     *
     * @param val 整数
     * @return 字节数组
     */
    public static byte[] intToBytes(int val) {
        ByteBuffer buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(val);
        byte[] b = new byte[4];
        buf.flip();
        buf.get(b);
        return b;
    }

    /**
     * 将4个字节的字节数组转换为int整数。
     *
     * @param bytes 字节数组
     * @return 整数
     */
    public static int bytesToInt(byte[] bytes) {
        ByteBuffer buf = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).put(bytes);
        buf.flip();
        return buf.getInt();
    }

    /**
     * 将long整数转换为8个字节的字节数组。
     *
     * @param val 整数
     * @return 字节数组
     */
    public static byte[] longToBytes(long val) {
        ByteBuffer buf = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).putLong(val);
        byte[] b = new byte[8];
        buf.flip();
        buf.get(b);
        return b;
    }

    /**
     * 将8个字节的字节数组转换为long整数。
     *
     * @param bytes 字节数组
     * @return 整数
     */
    public static Long bytesToLong(byte[] bytes) {
        ByteBuffer buf = ByteBuffer.allocate(8).order(ByteOrder.LITTLE_ENDIAN).put(bytes);
        buf.flip();
        return buf.getLong();
    }

}
